require "resources/azure/azure_backend"

module Inspec::Resources
  class AzureVirtualMachine < AzureResourceBase
    name "azure_virtual_machine"

    desc '
      InSpec Resource to test Azure Virtual Machines
    '

    supports platform: "azure"

    # Constructor for the resource. This calls the parent constructor to
    # get the generic resource for the specified machine. This will provide
    # static methods that are documented
    #
    # @author Russell Seymour
    def initialize(opts = {})
      # The generic resource needs to pass back a Microsoft.Compute/virtualMachines object so force it
      opts[:type] = "Microsoft.Compute/virtualMachines"
      super(opts)

      # Find the virtual machines
      resources

      create_tag_methods
    end

    # Method to catch calls that are not explicitly defined.
    # This allows the simple attributes of the virtual machine to be read without having
    # to define each one in turn.
    #
    # rubocop:disable Metrics/AbcSize
    #
    # @param symobl method_id The symbol of the method that has been called
    #
    # @return Value of attribute that has been called
    def method_missing(method_id)
      # Depending on the method that has been called, determine what value should be returned
      # These are set as camel case methods to comply with rubocop
      image_reference_attrs = %w{sku publisher offer}
      osdisk_attrs = %w{os_type caching create_option disk_size_gb}
      hardware_profile_attrs = %w{vm_size}
      os_profile_attrs = %w{computer_name admin_username}
      osdisk_managed_disk_attrs = %w{storage_account_type}

      # determine the method name to call by converting the snake_case to camelCase
      # method_name = self.camel_case(method_id.to_s)
      method_name = method_id.to_s.split("_").inject([]) { |buffer, e| buffer.push(buffer.empty? ? e : e.capitalize) }.join
      method_name.end_with?("Gb") ? method_name.gsub!(/Gb/, &:upcase) : false

      if image_reference_attrs.include?(method_id.to_s)
        properties.storageProfile.imageReference.send(method_name)
      elsif osdisk_attrs.include?(method_id.to_s)
        properties.storageProfile.osDisk.send(method_name)
      elsif hardware_profile_attrs.include?(method_id.to_s)
        properties.hardwareProfile.send(method_name)
      elsif os_profile_attrs.include?(method_id.to_s)
        properties.osProfile.send(method_name)
      elsif osdisk_managed_disk_attrs.include?(method_id.to_s)
        properties.storageProfile.osDisk.managedDisk.send(method_name)
      end
    end

    # Return the name of the os disk
    #
    # @return string Name of the OS disk
    def os_disk_name
      properties.storageProfile.osDisk.name
    end

    # Determine if the OS disk is a managed disk
    #
    # @return boolean
    def has_managed_osdisk?
      defined?(properties.storageProfile.osDisk.managedDisk)
    end

    # Does the machine have any NICs connected
    #
    # @return boolean
    def has_nics?
      properties.networkProfile.networkInterfaces.count != 0
    end

    # How many NICs are connected to the machine
    #
    # @return integer
    def nic_count
      properties.networkProfile.networkInterfaces.count
    end

    # Return an array of the connected NICs so that it can be tested to ensure
    # the machine is connected properly
    #
    # @return array Array of NIC names connected to the machine
    def connected_nics
      nic_names = []
      properties.networkProfile.networkInterfaces.each do |nic|
        nic_names << nic.id.split(%r{/}).last
      end
      nic_names
    end

    # Whether the machine has data disks or not
    #
    # @return boolean
    def has_data_disks?
      properties.storageProfile.dataDisks.count != 0
    end

    # How many data disks are connected
    #
    # @return integer
    def data_disk_count
      properties.storageProfile.dataDisks.count
    end

    # Does the machine allow password authentication
    #
    # This allows the use of
    #   it { should have_password_authentication }
    # within the InSpec profile
    #
    # @return boolean
    def has_password_authentication?
      password_authentication?
    end

    # Determine if the machine allows password authentication
    #
    # @return boolean
    def password_authentication?
      # if the osProfile property has a linuxConfiguration section then interrogate that
      # otherwise it is a Windows machine and that always has password auth
      if defined?(properties.osProfile.linuxConfiguration)
        !properties.osProfile.linuxConfiguration.disablePasswordAuthentication
      else
        true
      end
    end

    # Has the machine been given Custom Data at creation
    #
    # This allows the use of
    #    it { should have_custom_data }
    # within the InSpec Profile
    #
    # @return boolean
    def has_custom_data?
      custom_data?
    end

    # Determine if custom data has been set
    #
    # @return boolean
    def custom_data?
      if defined?(properties.osProfile.CustomData)
        true
      else
        false
      end
    end

    # Are any SSH Keys assigned to the machine
    #
    # This allows the use of
    #    it { should have_ssh_keys }
    # within the InSpec Profile
    #
    # @return boolean
    def has_ssh_keys?
      ssh_keys?
    end

    # Determine if any ssh keys have been asigned to the machine
    #
    # @return boolean
    def ssh_keys?
      if defined?(properties.osProfile.linuxConfiguration.ssh)
        properties.osProfile.linuxConfiguration.ssh.publicKeys != 0
      else
        false
      end
    end

    # Return the number of ssh keys that have been assigned to the machine
    #
    # @return integer
    def ssh_key_count
      if defined?(properties.osProfile.linuxConfiguration.ssh)
        properties.osProfile.linuxConfiguration.ssh.publicKeys.count
      else
        0
      end
    end

    # Determine is the specified key is in the ssh_keys list
    #
    # @return array Array of the public keys that are assigned to allow for testing of that key
    def ssh_keys
      # iterate around the keys
      keys = []
      properties.osProfile.linuxConfiguration.ssh.publicKeys.each do |key|
        keys << key.keyData
      end
      keys
    end

    # Does the machine have boot diagnostics enabled
    #
    # @return boolean
    def has_boot_diagnostics?
      if defined?(properties.diagnosticsProfile)
        properties.diagnosticsProfile.bootDiagnostics.enabled
      else
        false
      end
    end

    # Return the URI that has been set for the boot diagnostics storage
    #
    # @return string
    def boot_diagnostics_storage_uri
      properties.diagnosticsProfile.bootDiagnostics.storageUri
    end

    # If this is a windows machine, returns whether the agent was provisioned or not
    #
    # @return boolean
    def has_provision_vmagent?
      if defined?(properties.osProfile.windowsConfiguration)
        properties.osProfile.windowsConfiguration.provisionVMAgent
      else
        false
      end
    end

    # If a windows machine see if automatic updates for the agent are enabled
    #
    # @return boolean
    def has_automatic_agent_update?
      if defined?(properties.osProfile.windowsConfiguration)
        properties.osProfile.windowsConfiguration.enableAutomaticUpdates
      else
        false
      end
    end

    # If this is a windows machine return a boolean to state of the WinRM options
    # have been set
    #
    # @return boolean
    def has_winrm_options?
      if defined?(properties.osProfile.windowsConfiguration) && defined?(properties.osProfile.windowsConfiguration.winrm)
        properties.osProfile.windowsConfiguration.winrm.protocol
      else
        false
      end
    end
  end
end
